package cn.flying.cloud.auth.security.filter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Map;

import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import cn.flying.cloud.auth.security.details.CustomUserDetailService;
import cn.flying.cloud.auth.security.exception.TokenExpiredException;
import cn.flying.cloud.auth.security.exception.TokenIllegalException;
import cn.flying.cloud.auth.security.handler.CustomUnauthorizedEntryPointHandler;
import cn.flying.cloud.base.common.constants.Constants;
import cn.flying.cloud.base.common.enums.RtCodeEnum;
import cn.flying.cloud.base.common.utils.Ft;
import cn.flying.cloud.base.redis.service.RedisService;

/**
 * 对所有请求进行过滤，验证携带的Token，验证成功后，将登录主体信息放入 SecurityContextHolder 中即可，
 * BasicAuthenticationFilter继承于OncePerRequestFilter==》确保在一次请求只通过一次filter，而不需要重复执行
 *
 * @author: admin
 * @date: 2023年06月16日 14:54
 * @version: 1.0
 */
public class JwtPreAuthenticationFilter extends BasicAuthenticationFilter {

    private CustomUserDetailService customUserDetailService;
    private RedisService redisService;
    private CustomUnauthorizedEntryPointHandler customUnauthorizedEntryPointHandler;

    /**
     * @param authenticationManager the bean to submit authentication requests to
     */
    public JwtPreAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    /**
     * 从request中读取Token
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        //校验token
        //从头部获取token信息
        String token = request.getHeader(HttpHeaders.AUTHORIZATION);
//        String token = request.getHeader(Constants.AUTH_TOKEN);
        //如果请求头部没有获取到token，则从请求的参数中进行获取
        if (Ft.String.isBlank(token)) {
            token = request.getParameter(Constants.ACCESS_TOKEN);
        }
        //如果请求参数中也不存在token信息，此处不处理，由后续filter处理
        if (Ft.String.isBlank(token)) {
            super.doFilterInternal(request, response, chain);
            return;
        }
        token = token.replace("Bearer ", "");
        if (Ft.Jwt.isExpired(token)) {
            throw new TokenExpiredException(RtCodeEnum.R10005.getDesc());
        }
        // TODO
//        try {
//            boolean validated = Ft.Jwt.validateToken(token);
//            if (!validated) {
//                throw new TokenExpiredException(RtCodeEnum.R10005.getDesc());
//            }
//        } catch (Exception e) {
//            throw new RuntimeException("校验Token失败-->", e);
//        }
        Map<String, Object> map = Ft.Jwt.parseToken(token);
        if (map == null) {
            throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
        }
        //请求中有Token，则判断redis中是否存在该token
        String tokenKey = Constants.ACCESS_TOKEN + ":" + map.get("jti");
        try {
            String redisToken = (String) redisService.get(tokenKey);
            //如果redis里面没有token,说明该token失效
            if (Ft.String.isBlank(redisToken)) {
                throw new TokenExpiredException(RtCodeEnum.R10005.getDesc());
            }
            //如果用户传入token和Redis中的token不一致，则验证失败
            if (!token.equals(redisToken)) {
                throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
            }
            //如果存在token，则从token中解析出用户名
            String username = (String) map.get("sub");
            //如果用户名为空，则解析失败
            if (Ft.String.isBlank(username)) {
                throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
            }
            //从数据库获取用户信息
            UserDetails userDetails = customUserDetailService.loadUserByUsername(username);
            //判断用户信息是否为空
            if (userDetails == null) {
                throw new TokenIllegalException(RtCodeEnum.R10005.getDesc());
            }
            //创建身份验证对象
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            //将认证主体设置到Spring Security上下文
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        } catch (AuthenticationException e) {
            customUnauthorizedEntryPointHandler.commence(request, response, e);
            //这里要结束程序执行，不然后续的filter还会执行，造成报错
            return;
        } catch (Exception e) {
            AuthenticationException exception = new TokenIllegalException(RtCodeEnum.R10005.getDesc(), e);
            customUnauthorizedEntryPointHandler.commence(request, response, exception);
            //这里要结束程序执行，不然后续的filter还会执行，造成报错
            return;
        }
        super.doFilterInternal(request, response, chain);
    }

    public CustomUserDetailService getLoginUserDetailService() {
        return customUserDetailService;
    }

    public void setLoginUserDetailService(CustomUserDetailService customUserDetailService) {
        this.customUserDetailService = customUserDetailService;
    }

    public RedisService getRedisService() {
        return redisService;
    }

    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    public CustomUnauthorizedEntryPointHandler getUnauthorizedEntryPointHandler() {
        return customUnauthorizedEntryPointHandler;
    }

    public void setUnauthorizedEntryPointHandler(CustomUnauthorizedEntryPointHandler customUnauthorizedEntryPointHandler) {
        this.customUnauthorizedEntryPointHandler = customUnauthorizedEntryPointHandler;
    }
}
